#include "Renderer.h"
#include <gl\gl.h>                                // Header File For The OpenGL32 Library
#include <gl\glu.h>                               // Header File For The GLu32 Library
#include "Line.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include "BodyInfo.h"
#include <iostream>
#include <limits>
#include <cmath>
#include "Connection.h"

#pragma warning (disable : 4482)

#ifdef min
#undef min
#endif

using namespace std;

// --- GUI global variables ----
int	  gui_totalObjects=0;
int   gui_displayedObjects=0;
float gui_airjetForce=0.0f;
float gui_objectPerSecond=0.0f;
float gui_elasticity=0.0f;
float gui_friction=0.0f;
float gui_graphicsFreq=0.0f;
float gui_networkFreq=0.0f;
float gui_physicsFreq=0.0f;
// -----------------------------

Renderer* ptrRenderer=0;

float	msgBuffer;


void	TW_CALL SetCallback(const void *value, void *clientData)
{ 
	// update value
	*(float*)clientData = *((float*)value);
	msgBuffer = *(float*)value;
		
	// send message to server
	Message msg;
	msg.header.length = sizeof(float);
	msg.data = (void*)&msgBuffer;		

	if (clientData == (void*)&gui_elasticity) {
		msg.header.type = MsgType::MSG_UPD_ELASTICITY;
	}
	else if (clientData == (void*)&gui_friction) {
		msg.header.type = MsgType::MSG_UPD_FRICTION;
	}
	else if (clientData == (void*)&gui_graphicsFreq) {
		msg.header.type = MsgType::MSG_UPD_GRAPHICSFREQ;
	}
	else if (clientData == (void*)&gui_networkFreq) {
		msg.header.type = MsgType::MSG_UPD_NETWORK_FREQ;
	}
	else if (clientData == (void*)&gui_physicsFreq) {
		msg.header.type = MsgType::MSG_UPD_PHYSICS_FREQ;
	}

	// pass message 
	ptrRenderer->m_connection->sendMessage(msg);

}
void	TW_CALL GetCallback(void *value, void *clientData)
{ 
	if (clientData == (void*)&gui_totalObjects)
		*(int*)value = *(int*)clientData;
	else
		*(float*)value = *(float*)clientData;
}



void	Renderer::initGUI()
{
	ptrRenderer = this;

	// Initialise GUI (AntTweakBar)
	TwInit(TW_OPENGL, NULL);
	TwWindowSize(900, 600);
	twBar = TwNewBar("twBar");
	TwDefine(" twBar position='650 40' "); 
	
	TwAddVarCB(twBar, "Total Objects",		TW_TYPE_INT32,	0,				GetCallback, (void*)&gui_totalObjects, "");
	TwAddVarCB(twBar, "Displayed Objects",  TW_TYPE_INT32,	0,				GetCallback, (void*)&gui_displayedObjects, "");
	TwAddVarCB(twBar, "Airjet Force",		TW_TYPE_FLOAT,	0,				GetCallback, (void*)&gui_airjetForce, "");
	TwAddVarCB(twBar, "Objects received /s",TW_TYPE_FLOAT,	0,				GetCallback, (void*)&gui_objectPerSecond, "");
	TwAddVarCB(twBar, "Elasticity",			TW_TYPE_FLOAT,	SetCallback,	GetCallback, (void*)&gui_elasticity, "");
	TwAddVarCB(twBar, "Friction",			TW_TYPE_FLOAT,	SetCallback,	GetCallback, (void*)&gui_friction, "");
	TwAddVarCB(twBar, "Graphics Freq",		TW_TYPE_FLOAT,	SetCallback,	GetCallback, (void*)&gui_graphicsFreq, "");
	TwAddVarCB(twBar, "Network  Freq",		TW_TYPE_FLOAT,	SetCallback,	GetCallback, (void*)&gui_networkFreq, "");
	TwAddVarCB(twBar, "Physics  Freq",		TW_TYPE_FLOAT,	SetCallback,	GetCallback, (void*)&gui_physicsFreq, "");

}


float*	Renderer::getPtrObjectsPerSec()
{
	return &gui_objectPerSecond;
}


void	Renderer::setTotalObjects(int v) {
	gui_totalObjects = v;
}
void	Renderer::setAirjetForce(float v) {
	gui_airjetForce = v;
}
void	Renderer::setElasticity(float v) {
	gui_elasticity = v;
}
void	Renderer::setFriction(float v) {
	gui_friction = v;
}
void	Renderer::setGraphicsFreq(float v) {
	gui_graphicsFreq = v;
}
void	Renderer::setNetworkFreq(float v) {
	gui_networkFreq = v;
}
void	Renderer::setPhysicsFreq(float v) {
	gui_physicsFreq = v;
}



void	Renderer::startTimer()
{
    LARGE_INTEGER li;
    QueryPerformanceFrequency(&li);
    freq = static_cast<double>(li.QuadPart);

    QueryPerformanceCounter(&li);
    start = li.QuadPart;

}

float	Renderer::getNetworkFreq()
{
	return gui_networkFreq;
}

float	Renderer::getGraphicsFreq()
{
	return gui_graphicsFreq;
}

double	Renderer::getTime()
{
	 LARGE_INTEGER li;

    QueryPerformanceCounter(&li);
    __int64 current = li.QuadPart;

    return (double)(current/freq);
}

double	Renderer::getDeltaTime()
{
    LARGE_INTEGER li;

    QueryPerformanceCounter(&li);
    __int64 current = li.QuadPart;

    double dt = static_cast<double>(current - start) / freq;

    start = current;

    return dt;
}




int		Renderer::init(HWND hwnd, int width, int height)
{
	initialiseOpenGL(hwnd, width, height);
	
	initGUI();

	return 0;
}




bool	Renderer::isNan(Vector v)
{
	if (v.GetX() != v.GetX() || v.GetY() != v.GetY()) {
		return true;
	}

	if ( v.GetX() != 0 && fabsf( v.GetX() ) < std::numeric_limits<float>::min() ) {
		return true;
	}
	if ( v.GetY() != 0 && fabsf( v.GetY() ) < std::numeric_limits<float>::min() ) {
		return true;
	}


	return false;
}

void	Renderer::initialiseOpenGL(HWND hwnd, int width, int height)
{
	GLuint		PixelFormat;			// Holds The Results After Searching For A Match
	HGLRC		hRC=NULL;		// Permanent Rendering Context
	HWND		hWnd=NULL;		// Holds Our Window Handle

	static	PIXELFORMATDESCRIPTOR pfd=				// pfd Tells Windows How We Want Things To Be
	{
		sizeof(PIXELFORMATDESCRIPTOR),				// Size Of This Pixel Format Descriptor
		1,											// Version Number
		PFD_DRAW_TO_WINDOW |						// Format Must Support Window
		PFD_SUPPORT_OPENGL |						// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,							// Must Support Double Buffering
		PFD_TYPE_RGBA,								// Request An RGBA Format
		24,										// Select Our Color Depth
		0, 0, 0, 0, 0, 0,							// Color Bits Ignored
		0,											// No Alpha Buffer
		0,											// Shift Bit Ignored
		0,											// No Accumulation Buffer
		0, 0, 0, 0,									// Accumulation Bits Ignored
		16,											// 16Bit Z-Buffer (Depth Buffer)  
		0,											// No Stencil Buffer
		0,											// No Auxiliary Buffer
		PFD_MAIN_PLANE,								// Main Drawing Layer
		0,											// Reserved
		0, 0, 0										// Layer Masks Ignored
	};

	if (!(m_hDC=GetDC(hwnd)))							// Did We Get A Device Context?
	{
		MessageBoxA(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	}

	if (!(PixelFormat=ChoosePixelFormat(m_hDC,&pfd)))	// Did Windows Find A Matching Pixel Format?
	{
		MessageBoxA(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	}

	if(!SetPixelFormat(m_hDC,PixelFormat,&pfd))		// Are We Able To Set The Pixel Format?
	{
		MessageBoxA(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	}

	if (!(hRC=wglCreateContext(m_hDC)))				// Are We Able To Get A Rendering Context?
	{
		MessageBoxA(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	}

	if(!wglMakeCurrent(m_hDC,hRC))					// Try To Activate The Rendering Context
	{
		MessageBoxA(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	}

	ShowWindow(hwnd,SW_SHOW);						// Show The Window
	SetForegroundWindow(hwnd);						// Slightly Higher Priority
	SetFocus(hwnd);									// Sets Keyboard Focus To The Window

	if (height==0)										// Prevent A Divide By Zero By
	{
		height=1;										// Making Height Equal One
	}

	glViewport(0,0,width,height);						// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	//gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
	glOrtho(-25,25,-25,25,-10, 10);

	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix


	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);				// Black Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations
	

	
}

void	Renderer::changePosition(float x, float y)
{
	x*=zoom + 0.1f;
	y*=zoom + 0.1f;


	translation.Set(translation.GetX()+x, translation.GetY()+y);
}

void	Renderer::resetView()
{
	translation.Set(0,0);
	zoom = 25.0f;
}

void	Renderer::clear()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();									// Reset The Current Modelview Matrix

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	glOrtho(-zoom,+zoom, -zoom,+zoom,-10, 10);
	
	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix

	glTranslatef(translation.GetX(), translation.GetY() ,0);
}

void	Renderer::present()
{
	TwDraw();
	SwapBuffers(m_hDC);
}



float	toDegrees(float rad) 
{
	return rad*(180.0f/3.14f);
}

void	Renderer::draw(BodyInfo* circles, int nCircles)
{
	float m_radius = 0.8f;

	gui_displayedObjects = nCircles;


	for (int i=0; i<nCircles; i++)
	{

		/*if (isNan(circles[i].m_pos)) {
			std::cout << "BodyInfo.pos == NaN" << std::endl;
		}*/

		//std::cout << circles[i].m_pos.GetX() << " " << circles[i].m_pos.GetY() << std::endl;
		if (!circles[i].m_bActive)
			glColor3d(0.7, 0.7, 0.7);
		else
		{
			if (circles[i].m_mass == 1.5f)
				glColor3d(1,1,0);	
			else if (circles[i].m_mass == 2.5f)
				glColor3d(0,1,0);
			else if (circles[i].m_mass == 3.5f)
				glColor3d(1,0,0);
		}

		glPushMatrix();
			glTranslatef(circles[i].m_pos.GetX(), circles[i].m_pos.GetY(), 0);
			glBegin(GL_LINE_LOOP);
			//glColor3d(1, 0, 0);
			for(float angle = 0.0f; angle < 2*M_PI; angle += 0.1f)
			{
				glVertex2d(sin(angle)*m_radius,  cos(angle)*m_radius);
			}
			glEnd();


			glRotatef(toDegrees(circles[i].m_omega), 0,0,1);
			glBegin(GL_LINES);
				glVertex2d(0.0f, 0.0f);
				glVertex2d(m_radius, 0.0f);
			glEnd();

		glPopMatrix();
	}
	GLenum error = glGetError();

}


void	Renderer::draw(Vector* rope, int ropePoints)
{
	if (!bDrawSpring) return;

	glPushMatrix();
		glBegin(GL_LINES);

		glColor3d(0,1,0);

		for (int i=0; i<ropePoints-1; i++) {

			glVertex2d(rope[i].GetX(), rope[i].GetY());
			glVertex2d(rope[i+1].GetX(), rope[i+1].GetY());

		}

		glEnd();
	glPopMatrix();
}

void	Renderer::changeZoom(float v)
{
	zoom -= v*2;
	zoom = max(zoom, 0.0f);
}


void	Renderer::draw(Line* lines, int nLines)
{
	int linesToRender = 15;
	if (bDrawSpring)
		linesToRender = 14;

	for (int i=0; i<linesToRender; i++)
	{
		Vector color = lines[i].GetColor();
	
		glPushMatrix();
			glBegin(GL_LINES);
				glColor3d(color.GetX(), color.GetY(), 0);
				glVertex2d(lines[i].GetPos(0).GetX(), lines[i].GetPos(0).GetY());
				glVertex2d(lines[i].GetPos(1).GetX(), lines[i].GetPos(1).GetY());
			glEnd();
		glPopMatrix();
	}

	if (this->bAirjetActive) {
		glPushMatrix();
			glBegin(GL_LINES);
				glColor3d(0,0,1);
				glVertex2d(airjetStart.GetX(), airjetStart.GetY());
				glVertex2d(airjetEnd.GetX(),   airjetEnd.GetY());
			glEnd();
		glPopMatrix();
	}


}
